/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat;

import static br.ufpe.cin.imlm2.util.OptionsParser.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

import soot.PackManager;
import soot.Transform;
import soot.options.Options;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.TrapInspector;
import br.ufpe.cin.imlm2.jbeat.soot.report.ExceptionReporter;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.GeneratesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.RaisesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation.PropagatesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.traps.EmptyTrapRemover;
import br.ufpe.cin.imlm2.util.BuildClasspath;
import br.ufpe.cin.imlm2.util.OptionsParser;
import br.ufpe.cin.imlm2.util.ParsingException;
import br.ufpe.cin.imlm2.util.SystemProperties;

public class Main {

	public static void printUsage(){
		System.out.println("Usage:\n" +
				" java -jar jbeat.jar MainClass [jbeat_args | soot_args]* \n" +
				" Where jbeat_args :\n" +
				"\t " + PARAM_PREFIX + SHOW_USAGE_PARAM + "   (Show this help)\n" +
				"\t " + PARAM_PREFIX + FULL_REPORT_PARAM + ":[true | false] (Report exceptions of auto-generated methods?)\n" +
				"\t " + PARAM_PREFIX + SINGLE_FILE_PARAM + ":[true | false] (Generate report in a single file?)\n" +
				"\t " + PARAM_PREFIX + RUNTIME_EXCEPTIONS_PARAM + ":[true | false] (Consider exceptions raised by the runtime?)\n" +
				"\t " + PARAM_PREFIX + CALL_GRAPH_PARAM + ":NOME (Callgraph name to be used. Valid names are 'cha' " +
						"and 'spark'. Defaults to 'cha' (Class Hierarchy Analysis)\n" +
				"\t " + PARAM_PREFIX + INCLUDE_CLASSES_PARAM + ":[classe[,classe]*] (Force inclusion of the listed classes in the analysis.) \n" +
				"\t " + PARAM_PREFIX + EXCLUDE_CLASSES_PARAM + ":[classe[,classe]*] (Exclude the classes in the list from the analysis) \n" +
				"\t " + PARAM_PREFIX + EXCLUDE_PACKAGE_PARAM + ":[package[,package]*] (Exclude the packages in the" +
						" list from the analysis. To exclude all the subpackages from a package include" +
						" a '.*' in the end of the package name, e.g. java.lang.* )\n" +
				"\t " + PARAM_PREFIX + INCLUDE_PACKAGE_ON_REPORT_PARAM + ":[package[,package]*] (NOT IMPLEMENTED " +
						"- Force the inclusion of the packages in the list in the analysis) \n" +
				"\t " + PARAM_PREFIX + INCLUDE_CLASSES_ON_REPORT_PARAM + ":[classe[,classe]*] (Force the inclusion " +
						"of the classes in the list in the analysis. By default standard java library classes are not include in the report) \n" +
				"\t " + PARAM_PREFIX + WITH_TRAP_INSPECTION_PARAM + ":[true | false] (Use the handler inspection " +
						"algorithm to improve the analysis accuracy?)\n" +
				"\t " + PARAM_PREFIX + ANALYZE_STANDARD_LIBRARIES_PARAM + ":[true | false] (Analyze standard java" +
						" libraries and consider exceptions thrown by their methods? )\n" +
				"\t " + PARAM_PREFIX + REPORT_STANDARD_LIBRARIES_PARAM + "[true | false] (Report standard java" +
						" libraries? )\n" +
				"\t " + PARAM_PREFIX + AUTOGENERATED_PARAM + ":[true | false] (Analyze and consider auto-generated methods ?)\n" +
				"\t " + PARAM_PREFIX + EXCLUDE_REFLECTION_PARAM + ":[true | false] (Consider exceptions thrown by classes in java.lang.reflect.* ?)\n" +
				"\t " + PARAM_PREFIX + STATIC_INITIALIZERS_PARAM + ":[true | false] (Analyze and consider static initializers methods ('<clinit>') ?)\n" +
				"\t " + PARAM_PREFIX + REPORT_PATH_PARAM + ":PATH  (Output directory path. Default = \"/reports\")\n" +
				"\t " + PARAM_PREFIX + CLASS_PATH_PARAM + ":PATHS  (Classpath of the target program. A list of string containing paths to directories,\n" +
						"jars and class/source/jimple files to be included in the analysis. This should also include the Java Runtime Libraries that are going to be used by the target program.)\n" +
		"NOTES:\n- Arguments are CASE SENSITIVE (i.e. true !=  TrUe).\n- Unknown arguments will be passed as arguments to Soot.\n" +
		"- If the target.java.libraries system property is defined then it should contain a list of classpath " +
		"entries (directories and jars separated by OS specific path separators) of the Java Runtime Libraries that are going to be used by the target program." +
		" In this case the Java Runtime Libraries should NOT be included in the " + PARAM_PREFIX + CLASS_PATH_PARAM + " paramater.\n" +
		"- Lists are COMMA separated.");
	}

	public static void main(String[] args) throws FileNotFoundException, ParsingException {

		if(args.length > 0) {
			Options ops = Options.v();
			OptionsParser.parse(args);
			if(!OptionsParser.isShowUsage()){
				/* General options */
				ops.set_whole_program(true);
				ops.set_app(true);
				ops.setPhaseOption("cg." + OptionsParser.getCallGraph(), "enabled:true");
				ops.setPhaseOption("cg", "all-reachable:true");
				ops.setPhaseOption("cg", "implicit-entry:false");
				ops.set_main_class(getMainClass());
				ops.set_output_format(Options.output_format_jimple);
				if(SystemProperties.TARGET_JAVA_LIBRARIES == null)	ops.set_prepend_classpath(true);
				String cp = buildTargetClassPath();
				ops.set_soot_classpath(cp);
				
				EmptyTrapRemover etr = new EmptyTrapRemover();
				PackManager.v().getPack("wjtp").add(new Transform("wjtp.etr", etr));

				boolean rtLibs = OptionsParser.isAnalyzeStandardLibraries();
				boolean clinits = OptionsParser.isStaticInitializers();
				boolean autoGenerated = OptionsParser.isAutoGenerated();

				if(OptionsParser.isWithTrapInspection()) {
					TrapInspector ti = new TrapInspector(getClassExcludeList(),
							getPackageExcludeList(), rtLibs);
					PackManager.v().getPack("wjtp").add(new Transform("wjtp.ti", ti));
				}

				if(OptionsParser.isAnalyzeRuntime()){
					GeneratesCalculator gc = new GeneratesCalculator(getClassExcludeList(),
							getPackageExcludeList(), rtLibs);
					PackManager.v().getPack("wjtp").add(new Transform("wjtp.gc", gc));
				}

				RaisesCalculator rc = new RaisesCalculator(getClassExcludeList(),
						getPackageExcludeList(), rtLibs);
				PackManager.v().getPack("wjtp").add(new Transform("wjtp.rc", rc));
				PropagatesCalculator pc = new PropagatesCalculator(getClassExcludeList(),
						getPackageExcludeList(), rtLibs,clinits, autoGenerated);
				PackManager.v().getPack("wjtp").add(new Transform("wjtp.pc", pc));


				ExceptionReporter er = new ExceptionReporter(getReportPath());
				PackManager.v().getPack("wjtp").add(new Transform("wjtp.er", er));

				soot.Main.main(new String[]{getMainClass()});

			} else {
				printUsage();
			}
		} else {
			printUsage();
		}

	}

	private static String buildTargetClassPath() {
		List<String> targetCP = OptionsParser.getClasspath();
		if(SystemProperties.TARGET_JAVA_LIBRARIES != null){
			String[] dirs = SystemProperties.TARGET_JAVA_LIBRARIES.split(SystemProperties.PATH_SEPARATOR);
			for (String dir : dirs) {
				File directory = new File(dir);
				targetCP.addAll(BuildClasspath.buildFromCPEntries(directory, false));
				
			}
		}
		StringBuffer classpath = new StringBuffer();
		for (String path : targetCP) {
			classpath.append(path + SystemProperties.PATH_SEPARATOR);
		}
		if(classpath.length() > 0) {
			classpath.delete(classpath.lastIndexOf(SystemProperties.PATH_SEPARATOR), classpath.length());
		}
		return classpath.toString();
	}


}
